Technical Not TN2028
Threading Architectures

目次

このテクニカルノートでは、Mac OS 9とMac OS XのさまざまなスレッディングAPIについて説明し、それらがどのようにそれぞれのプラットフォームのコアオペレーティングシステムとやりとりしているのかを解説します。スレッディングを実装するための実用的なガイドではなく、各OSにおけるスレッディングアーキテクチャの概要です(ヒントやティップなども添えていきます)。

このテクニカルノートは、Mac OSにおけるスレッディングの動作方法を理解しようとしているデベロッパを対象にしています。Mac OS 9のスレッド化アプリケーションをMac OS Xに移植しようという方には特に役に立ちます。プラットフォームが違うため、以前は慎重に決めたことをもう一度考えてみる必要があるからです。

更新: [2001年8月8日]






はじめに

Mac OS 9 と Mac OS X のスレッディングの特性が異なっていても、驚くには値しないでしょう。Mac OS X(Carbon の一部として)は Mac OS 9 のスレッディング API をサポートしてはいますが、それにはまったく異なるコア OS を使っています。それにより、このコア OS がスレッディング API の動作に微妙な変化をもたらしています。

このテクニカルノートでは両方のプラットフォームにおけるスレッディングアーキテクチャの基本について説明します。基本的な設計思想がわかると、それぞれのプラットフォームのスレッドの扱いにおける微妙な違いを理解していただけるでしょう。最終的な目的は、スレッド化されたアプリケーションを各プラットフォーム上の動作に適合させるための調整をしやすくすることです。それがアプリケーションやシステム上のその他のアプリケーションの性能向上につながります。

重要:
Mac OS X の全体的なアーキテクチャにまだ慣れていない場合(Mach、BSD、および IOKit の区別がつかないなど)は、『 Inside Mac OS X: System Overview 』をお読みください

このテクニカルノートでは、特に Mac OS 9.1 とMac OS X 10.0.x のスレッディングアーキテクチャについて説明します。従来の Mac OS の旧バージョンとは異なっており、Mac OS X の将来的なバージョンでも異なる可能性があります。Mac OS X は急速に進化しつつあるシステムです。このドキュメントは、Mac OS X のスレッディング機能について考えるときのガイドとして利用していただき、このプラットフォームにおけるスレッディングの最終的な定義であるとは考えないでください。

先頭に戻る

Mac OS 9 のスレッディング

このセクションでは Mac OS 9 のスレッディングについて説明します。Mac OS 9 には 2 つのスレッディング API があります。

  • Thread Manager はプロセス内で協調的にスケジュールされたスレッドを提供します。

  • MP タスクはナノカーネルによってプリエンプティブにスケジュールされています。

Mac OS 9 のスレッディングを理解するには、これらのスレッディング API に加えて、Process Manager に実装されている Mac OS 9 のプロセススケジューリングを忘れてはなりません。

これらの API それぞれのプログラミングに関する技術文書については、このテクニカルノートの巻末にある 参考文献 のセクションを参照してください。


重要:
「MP タスク」の「MP」とは「マルチプロセッサ」のことですが、MP タスクは単一のプロセッサシステム上でも使用でき、プリエンプティブにスケジュールすることが可能です。MP タスクを使ったプリエンプティブスレッディングを利用するために、マルチプロセッサシステムは必要ありません。.


重要:
スレッディングという用語はわかりにくいことが多く、特に多くの背景から用語を受け継いできている Mac OS X のようなシステムではそれが顕著です。特にわかりにくいのは「タスク」という用語です。MP API ではプロセスを使った実行のスレッドを指して「MP タスク」と呼んでいます。一方、Mach はスレッドやメモリ、ポート(一般的にはプロセスとして知られているアイディア)などのリソース全体を指して「Mach タスク」と呼んでいます。このテクニカルノートではこの 2 つの概念を区別するために「タスク」を単体では使わず、必ず「MP タスク」あるいは「Mach タスク」のどちらかを使うことにします。


注意:
このセクションのタイトルは「Mac OS 9 のスレッディング」であって「従来の Mac OS のスレッディング」ではありません。このセクションでは、次のような、古い従来の Mac OS のスレッディングコンセプトについては取り扱いません。
  • Thread Manager 登場以前には一般的だったカスタムスレッディングライブラリ
  • 68K システムでのみサポートされている、プリエンプティブにスケジュールされた Thread Manager スレッド
  • Multiprocessing Service バージョン 2.0 以前(Mac OS 8.6)。これはナノカーネル外の共有ライブラリによって実装されたもので、仮想メモリがオフの時のみ動作していたので、それほど実用的ではありませんでした。


MP タスクのない Mac OS 9

一時的に MP タスクを除外した場合、Mac OS 9 のスレッディングアーキテクチャを図 1 に示します。

MacOS09.gif
図1 Mac OS 9 のスレッディング

このアーキテクチャでは、スレッディングはすべて協調的に行われます。プロセスが WaitNextEvent のような yield 関数を呼び出したとき、各プロセスは Process Manager によってラウンドロビン方式でスケジュールされます。それぞれのプロセス内に協調的にスケジュールされたスレッドのセットがあって、プロセスが YieldToAnyThread(あるいは YieldToThread)を呼び出すと Thread Manager によってスケジュールされます。

このダイアグラムから以下の結論が導けます。

  • 各プロセスは Thread Manager のそれぞれのコピーをインスタンス化し、そのプロセスのコンテキスト内でのみスレッドのスケジューリングを行います。
  • すべてのスレッドは、固有のプロセスコンテキストに属します。
  • Thread Manager スレッドは必ずプロセスにアタッチされています。システムプロセスは存在しません。したがって、システムの Thread Manager スレッドを作成する方法はありません。
  • スレッドの明け渡しを行うとき、必ずそのスレッドが属しているプロセス内のスレッドに明け渡しが行われます。1 つのプロセスが、他のプロセス内のスレッドに直接明け渡しを行うことはできません。スレッドはすべて自らの属するメインスレッドに明け渡しを行い、メインスレッドは WaitNextEvent を呼び出し、それによって Process Manager が他のプロセスのメインスレッドのスケジュールを行い、そこからそのプロセスに属するスレッドに明け渡しが行われるのです。
  • ユーザインタフェースイベントを取り扱うことなく、他のプロセスに明け渡しを行うことはできません。このトピックについては、DTS Q&A PS 06 「Yielding Time Without Getting Events 」を参照してください。
  • 協調的なスレッドはそれぞれのプロセスにローカルで、いかなる「カーネル」リソースも含まないため、とても軽量でスレッド間のコンテキストスイッチも経済的です。

注意:
ここまでの包括的な説明の中には、特記すべき例外があります。たとえば、NSL などのような Apple ソフトウェアでは、たしかにシステム規模の Thread Manager スレッドを作成します。しかし、その作成において使用するメカニズムは未公開であり、それほどいいものでもありません。さらに、自分のいるメインスレッドではないスレッドから WaitNextEvent を呼び出してもいいのですが、混乱を引き起こしがちです。それは、WaitNextEvent を呼び出すには必ずユーザイベントを受け取らなければいけないからで、メインではないスレッドでユーザイベントを処理しなければならないからです。

Mac OS 9 のスレッディングで重要な特徴の 1 つに、Process Manager のプロセスが非常に不経済なことがあげられます。大量のリソース(プロセスにはサイズ固定のメモリパーティションがあります)を消費する上に、歴史的な理由からプロセス間のコンテキストスイッチに非常に時間がかかるのです。

MP タスクのある Mac OS 9

MP タスクを考慮に入れると、Mac OS 9 のスレッディングアーキテクチャは、図 2 に示すように多少複雑になります。

MacOS9WithMP.gif
図2 MP タスクを示す Mac OS 9 のスレッディング

協調的な環境全体が 1 つの MP タスク内で動いています。このタスクはブルータスクとして知られています。Process Manager のプロセスと Thread Manager のスレッドはすべて、ブルータスクによって実行されます。システムやアプリケーションによって作成される、その他の MP タスクは個別のエンティティとして実行されます。ナノカーネルはタスクがプリエンプティブな形でプロセッサ(あるいはマルチプロセッサ)上で動くようスケジュールします

重要:
図 2 はナノカーネルタスクスケジューラをごく単純化して示したものです。スケジューラはここには現れていないたくさんの複雑な問題を扱う必要があります。
  • ブロックされたタスク(キューを待つなど)は、実行してはいけません。
  • MP システム上では、プロセッサごとにタスクをスケジュールする必要があります。
  • さまざまなタスクにおけるスケジューリングのウェイトやレイテンシーの必要条件を考慮しておかなければなりません。

このアーキテクチャに関して、以下の点に注意してください。

  • ナノカーネルは、ブルータスクを含むすべての MP タスクを、自己の内部的なスケジューリングに関するポリシーを使って、プリエンプティブにスケジュールします。そのポリシーに手を加えること(MPSetTaskWeight を呼び出すなど)は可能ですが、Process Manager や Thread Manager などのようにスケジューリングポイントを直接コントロールすることはもはやできません。実際、ブルータスクが 68K のエミュレータを実行しているときには、1 つの 68K 命令を経由している途中で他の MP タスクによってプリエンプトされる可能性もあります。
  • システムはプロセスが作成した MP タスクを追跡し、プロセスが終了したときにそれらの MP タスクも終了していることを確認します。この関係は図 2 にピンクのラインで示されています。しかし、ナノカーネルのタスクスケジューラに関しては、MP タスクはすべて同等であると考えられます。ナノカーネルはプロセス A の MP タスクをプリエンプトし、すぐにプロセス B の MP タスクの 1 つに取りかかれるのです。
  • しかし、ナノカーネルにはブルータスクに関していくつか例外があります。たとえば、MP タスクがページフォールトを受けると、ナノカーネルはそのタスクを遮断し、ページフォールトを解決するためにブルータスクにソフトウェア割り込みを送ります。しかし、外部デベロッパの視点からは、MP タスクはみな同じように作成されています。
  • ブルータスク上で実行されているソフトウェアだけが 68K エミュレータへアクセスできます。つまり、MP タスクが直接使用できるのは限られたシステムサービスだけなのです。この一連のサービスについては DTS テクニカルノート 2006「MP-Safe Routines 」をお読みください。非 MP セーフなルーチンは MPRemoteCall を通じて呼び出すことができます。
  • システムは MP タスクとブルータスク間に長短両方のレイテンシーの通信を提供します。
    1. MP タスクは、MPRemoteCall(kMPAnyRemoteContext か kMPOwningProcessRemoteContext を context パラメータに渡す)を使って、ブルータスクがシステムタスク時に関数を呼び出すよう要求することができます)。この関数はシステムタスク時に実行されるため、スケジュール時と実際の実行時間との間にはかなりの遅延が見られます。システムは、ブルータスクが Process Manager の標準的なスケジュール解除ルーチン(WaitNextEvent、EventAvail など)から 1 つを呼び出すまで待って、それから関数を実行しなければなりません。しかし、関数はシステムタスク時に実行されるので、実質的にはすべての Mac OS システムコールへアクセスできます。このサービスの使用例として、MPAllocateAligned がプロセスの MP メモリプールを増やす必要があるとき、基礎にある Mac OS の Memory Manager をシステムタスク時に呼び出されなければならないので、MPRemoteCall を使います。
    2. Mac OS 9.0 およびそれ以降のバージョンでは、MP タスクはブルータスクにソフトウェア割り込みを送ることができます。このソフトウェア割り込みは、Process Manager がスケジュール解除ルーチンを呼び出すのを待つ必要がないので、ほとんど遅延なく呼び出すことができます。しかし、ソフトウェア割り込みは割り込み時に実行され、割り込みされないシステムサービスしか呼び出すことができません(DTS テクニカルノート 1104「Interrupt-Safe Routines」を参照のこと)。コードを記述する際には MPRemoteCall(Mac OS 9.1 以降では kMPInterruptRemoteContext を context パラメータに渡す)あるいは DTInstall(Mac OS 9.0以降)でソフトウェア割り込みをスケジュールすることができます。

    注意:
    Mac OS 9.0 以降では、このソフトウェア割り込みのメカニズムによって MP タスクが非同期 File Manager ルーチンを呼び出すことができます。MP タスクが File Manager を呼び出すと、File Manager はその要求をキューに置き、ソフトウェア割り込みがブルータスク内での割り込み時に実際に行われるようにスケジュールすることができます。
    重要:
    上記のソフトウェア割り込みのメカニズムと、「Designing PCI Cards and Drivers for Power Macintosh Computers」で記述したソフトウェア割り込みとを混同しないようにしてください。概念も名前も似てはいますが、そのメカニズムは全く異なります。実際、後者は Copland の構造体で、これまでの Mac OS でも適切に実装されてはいませんでした。

先頭に戻る

Mac OS X のスレッディング

このセクションでは Mac OS X のスレッディングについて記述します。5 つの異なる API が提供されています。

  1. Mach スレッドはシステムの最下層レベルのスレッディングです。
  2. POSIX スレッド (pthread) は Mach スレッドのすぐ上のレイヤにあります。
  3. Cocoa スレッド (NSThread) は pthread のすぐ上のレイヤにあります。
  4. Carbon MP タスクは Mac OS 9 における MP タスクと API 互換で、pthread の上のレイヤにあります。
  5. Carbon Thread Manager の協調的スレッドは、これも Mac OS 9 における Thread Manager の協調的スレッドと API 互換で、同じように pthread のすぐ上のレイヤにあります。

Carbon Specification 」では、Mac OS X で MP タスクや Thread Manager のコードを Carbon に移植する際の問題について説明しています。

Mac OS X におけるさまざまなスレッディング API について考えるとき、それらをレイヤ階層に並べてみると便利です。たとえば、各 MP タスクは pthread のすぐ上のレイヤにあり、各 pthread は Mach スレッドのすぐ上のレイヤにあります。この階層を図 3 に示しています。

MacOSXThreadHierarchy.gif
図3 Mac OS X スレッドレイヤ階層

これらの API それぞれのプログラミングに関する技術文書については、このテクニカルノートの巻末にある 参考文献 のセクションを参照してください。

注意:
Carbon は、pthread のすぐ上のレイヤにあり Thread Support(TS) として知られる未公開のスレッディングアブストラクションレイヤを実装しています。MP タスクも Thread Manager スレッドも現行では TS スレッドのすぐ上のレイヤにあります。Thread Support はパブリック API として公開されておらず、Mac OS X の将来的なリリースからは削除される可能性もあります。

スレッド API の選択

公開されているスレッディング API には 5 種類あり、どれを使うか決めかねることもあるでしょう。ここに一般的なガイドラインを示します。

  • ユーザスペース内でのプロセスは直接 Mach スレッドを作成してはいけません。
  • Cocoa 環境内で作業しているときには、もちろん NSThread が最適です。しかし pthread も使用できますし、場合によっては選択すべき時もあります。たとえば Cocoa ユーザインタフェースが、すでに pthread を使用している既存のコードのフロントエンドになっているときです。
  • Carbon 環境内で作業しているときには、まずプリエンプティブなスレッドを使用できるのか、あるいは協調的なスレッドを使用しなければならないのかを見極める必要があります。多くの Carbon 呼び出しは、Mac OS X においてさえも、プリエンプティブなスレッドでは使用できません。しかし、プリエンプティブなスレッドを使えるのであればそうすべきです。うまく動作している Mac OS X のアプリケーションに協調的なスレッドを統合する のは困難です。
  • Carbon アプリケーションが協調的なスレッドを使用する必要がある場合、選択肢は Thread Manager しかありません。
  • Carbon アプリケーションがプリエンプティブなスレッドを使えるのであれば、pthread か MP タスクのいずれかを選択できます。その際、以下の点に注意してください。
    • pthread は Mac OS 9 では使用できません。
    • Mac OS X は pthread に CFM バインディングを提供しません。CFM アプリケーションから pthread を呼び出すときには、オリジナルの CFM-to-Mach-O グルーを作成しなければなりません。CarbonLib SDK にはその作成方法を示すサンプルが含まれています。
  • すべてのケースで、同一プロセス内に複数のスレッドを混在させることができます。たとえば、Carbon アプリケーションで、協調的なスレッドと MP タスクの両方を作成することができます。あるいは Cocoa アプリケーションで NSThread と pthread を使用することもできます。
  • 同じスレッド上でスレッド API を混在させるときには注意してください。この問題については、 後のセクション で説明します。

Mac OS 9 と比べ、Mac OS X ではすべてのスレッドがだいたい同じコストになります。pthread や MP タスク、協調的なスレッドを NSThread としてラッピングする際のオーバーヘッドは、Mach ベースのスレッドのオーバーヘッドと比べて小さくなります。これはまた、協調的なスレッド(他のタイプのスレッドに比べて)は、Mac OS 9 上にあるときと比べるとより不経済だということも意味しています。Mac OS X におけるスレッドのコストの詳細については『 Inside Mac OS X: Performance 』を参照してください。

注意:
アップルでは Thread Manager スレッドごとに Mach スレッドを使用するのは不経済だと考えており、将来的にはこのコストを削減する方法を検討しています。しかし、Mac OS X 10.0.x では変更されておらず、Mac OS X 10.1 においても予定しておりません。

スレッドのコンテキストスイッチに要する時間は、どのスレッディング API を選択するかで異なります。何気ないコンテキストスイッチやカーネル内の I/O ブロックでは、そのスレッドをどのように作成してもオーバーヘッドは同じになります。その他のコンテキストスイッチ(たとえば、セマフォを待つなど)にかかるオーバーヘッドは選択した API によりますが、ハイレベル API は通常より時間がかかります。

Mac OS X のスレッドスケジューリング

これまでの説明してきたスレッド階層に関する結論の 1 つとして、究極的にはすべてのスレッドは Mach スレッドで表現できるということです。Mach に関する限り、すべてのスレッドは同等です。Mach スケジューラは、実行すべきスレッドを選択するとき必ず自らの スケジューリングポリシー に従います。Mach スレッドが pthread なのか、あるいは MP タスクや協調的スレッドなのかなどは一切関係ありません。したがって、図 4 に示すように、Mach スケジューリングは 1 つの均一な輪として表現できます。

MacOSXThreads.gif
図4 Mach スレッドスケジューラ(ごく単純化したもの)


重要:
図 4 は Mach スレッドスケジューラをごく単純化して示したものです。スケジューラは、ここには示されていないたくさんの複雑な問題を扱う必要があります。

  • ブロックされたスレッド(I/O を待っている、あるいはミューテックスなど)は実行してはいけません。
  • MP システムでは、プロセッサごとにスレッドをスケジュールする必要があります。
  • さまざまなスレッドのスケジューリングポリシーや優先順位に従わなければなりません。


さらに、Mach スレッドはそれぞれ特定の Mach タスクが所有します。スレッドとタスクの関係は図 4 に示していませんが、 スケジューリングポリシー の中にはこの関係が重要なものもあります。

協調的スレッドが実装されている方法を理解するには、この図を横から見て、基調となる Mach スレッドの上にどのようにアブストラクションがレイヤとして存在しているかを確かめる必要があります。図 5 にそれを示します。各アブストラクションの一番下のレイヤは Mach スレッドで、これは実際にはカーネルがスケジュールしています。Mach スレッドの中には生の形のままで存在しているものもあります(このようなスレッドは一般的に カーネル 内部で実行されるように作成されています)。ユーザスペース Mach スレッドは、すべて上のレイヤに pthread を載せています。Cocoa アプリケーションによって作成されたスレッドは上部レイヤに NSThread を載せています。Carbon は Carbon 自身のためにも、あるいは上位レベルのスレッディング API を実装するためにもスレッドを作成することができます。スレッドが Carbon によって内部で使用される ために作成される場合、pthread のとても単純なラッパーが実装されます。Carbon アプリケーションの命令によって Carbon がスレッドを作成された場合、それも pthread になりますが、Thread Manager スレッドか MP タスクがその上のレイヤに存在します。

MacOSXThreadsStacked.gif
図5 協調的スレッドを持つ Mac OS X スレッドアブストラクションのトークンリング

Carbon は単一プロセス内の Thread Manager スレッドがすべて協調的にスケジュールされていることを保証します。各プロセスは、プロセス内の協調的スレッド間で渡される特別な同期トークン(トークンは Mach メッセージとして実装されています)を持っています。協調的スレッドがトークンを持っている場合は、そのスレッドは実行可能になります。持っていない場合は、トークンを待ってブロックされます。トークンを持った協調的スレッドが YieldToAnyThread を呼び出すと、Carbon は次の協調的スレッドを選択してトークンをそのスレッドに渡します。新しいスレッドの実行が開始され、はじめのスレッドはそのトークンを待ってブロックされます。

このトークン渡しの配列は図 5 に赤いラインで示されています。

Mach スケジューリングポリシーと優先順位

Mach スレッドのポリシーは、そのスレッドをスケジュールするために使用されるアルゴリズムを制御します。Mac OS X で推奨されるスレッドポリシーは以下の通りです。

  • 標準ポリシー (THREAD_STANDARD_POLICY)。スレッドはシステムが定義した公平なアルゴリズムによってスケジュールされます。
  • 時間制約ポリシー (THREAD_TIME_CONSTRAINT_POLICY)。スレッドはリアルタイムの制約に従ってスケジュールされます。
  • 優先順位ポリシー (THREAD_PRECEDENCE_POLICY)。同一タスクの他のスレッドと比較して、タスクがそのスレッドの重要性を見極められるようにします。

各ポリシーにはそれぞれいくつかポリシーパラメータがあって、スレッドがそのポリシーの内部でどう動作するかを制御しています。それらのパラメータは、スレッドプライオリティの一般的なアイディアにほぼ相当しています。たとえば、優先順位ポリシーを使ったスレッドは、同一タスク内にある他のスレッドに比べてそのスレッドのプライオリティを制御する「重要性」パラメータを持っています。しかし、このきわめて単純化した等価の図式も、ずっと複雑な時間制約ポリシーの前では崩れ去ります。ここではポリシーパラメータは実際にはいくつかのリアルタイム値なのです。

各スレッディング API はそれぞれの考えに基づいてスレッドの優先順位を決めています。たとえば、MP タスクにはタスクウエイトという概念(MPSetTaskWeight によって定義されます)があり、pthread にはスレッドスケジューリングパラメータ(pthread_setschedparam によってセットされます)があります。各スレッディング API はそれぞれの優先順位の概念に従って、基礎となる Mach ポリシーやポリシーパラメータにマップします。一般にこれらのアルゴリズムは未公開で、今後変更される可能性もありますが、 Darwin のソースコード をお読みいただくと pthread がどのようにしてこのマッピングを行っているのか、より詳しく学ぶことができます。

Mac OS で推奨されるスレッドポリシーの詳細については、<mach/thread_policy.h> を参照してください。Mac OS X は、古くて未使用状態になっているスレッドポリシーの一部もサポートしています。<mach/policy.h> で定義されていますのでお読みください。

Mac OS X スレッドのいろいろ

このセクションでは、Mac OS X のスレッディング関連のさまざまなトピックについて説明します。

メインスレッド

メインスレッドはすべてのスレッディング API に有効なスレッドです。たとえば、メインスレッドから MPTaskIsPreemptive(MP API) と ThreadCurrentStackSpace(Thread Manager の API) を呼び出せます。これは 図 3 に示す階層の解釈からは厳密には外れますが、広く実用的だと見なされています。

Carbon と割り込み

従来の Mac OS は割り込み時にもたらされる I/O 完了ルーチンを使った非同期 I/O を多用しています。しかし、Mac OS X のコア OS はコールバックベースの非同期 I/O モデルをサポートしておらず、ユーザスペースコードは割り込み時には実行されません。Carbon はプリエンプティブスレッドを使った非同期 I/O 完了ルーチンをシミュレートします。

たとえば、非同期の File Manager 要求をすると、Carbon は単にその要求を内部キューに置き、その非同期ファイル I/O スレッドのスリープを解除します。そのスレッドがキューから最初の項目を受け取って同期的に実行し、その完了ルーチンを呼び出します。キューに項目がなくなると、そのスレッドはスリープ状態になります。結局、基礎となるコア OS には非同期ファイルシステム API がなくても、非同期的に操作されているように見えるわけです。

Carbon は、File Manager や Time Manager、Deferred Task Manager、Open Transport など、多くの異なった割り込み時コールバックベースの API でこれと似たテクニックを使っています。各サブシステムはコールバックを行うために自らの pthread を作成しています。

この設計により、いくつもの興味深い結果が生まれました。

  • 従来の Mac OS と同じように、Carbon I/O 完了ルーチンは各サブシステム内でシリアライズされています。たとえば、File Manager 非同期 I/O スレッドは 1 つしかありません。したがって、1 度に 1 つの完了ルーチンしか呼び出すことができません。これは一般的によいことと考えられています。
  • Carbon 内では、ファイルシステムのサブシステムには各プロセス内のすべての非同期ファイル I/O に対してスレッドが 1 つしかないため、非同期ファイル I/O はシリアライズされています。これは従来の Mac OS における File Manager とよく似ています。しかし、Mac OS X のコア OS の方は多くの未解決 I/O 要求をサポートしています。この機能を利用するには、複数のスレッドを作成して、各スレッドがそれぞれの同期的 I/O を行うようにすればいいのです。
  • Carbon の I/O 完了モデルは「完了するまで実行」しないという点で、従来の Mac OS とは異なっています。I/O 完了ルーチンはプリエンプティブスレッドによって実行されます。このスレッドは、プロセスのメインスレッドより高い優先度を持っていて、I/O 完了スレッドがそのメインスレッドによってプリエンプトされないという保証はありません。さらに、マルチプロセッサシステムを使っている場合、I/O 完了スレッドとメインスレッドは同時に実行することができます。もしアプリケーションの同期モデルが、I/O 完了ルーチンは完了するまで実行されるものとみなしているとすると、システム動作のこの変更のせいで、再現もデバッグも修正も困難なバグを作りかねません。
  • Carbon のすべての「割り込み」は実際にはプリエンプティブスレッドで実行されるので、割り込みをかけても安全なルーチンはすべてプリエンプティブしても安全(Mac OS X では)だと考えることができます。

ミックスマッチ

使用可能なスレッディング API すべてがそろうと、いずれかの時点でスレッドとその API を混ぜて使いたくなることもあります。たとえば MP タスク上で Mach スレッド API を呼び出したい、pthread 上で MP を呼び出したい場合です( 図 3 に示したレイヤ階層の仮定でいえば)。1 つのスレッドが他のスレッドの上のレイヤにあれば、一般的にはハイレベルスレッドでローレベル操作を行うことは可能です。たとえば、ほとんどの pthread 呼び出しは MP タスク上で行っても安全です。しかしこれにはいくつか考慮すべき問題があります。

最初の問題は、どのようにしてローレベルスレッドへの参照を得るかということです。pthread API だけが下位にあるスレッドへの参照を得る明確なメカニズムを提供できます(pthread_mach_thread_np が pthread に Mach スレッドを返します)。他のスレッド API はどれもこの機能を明確にサポートしていません。ローレベルスレッドへの参照を得る唯一の方法は、ハイレベルスレッドのコンテキスト内にいる間に、ローレベルの「自己」(あるいは「カレントスレッド」)API を呼び出すことです。たとえば、ある MP タスクの pthread を検索するには、その MP タスク内から pthread_self 呼び出しをすればいいのです。

次に、ローレベル操作が実際にハイレベルスレッドで機能するかということです。一般的には、ほとんどの pthread API がより上位レベルのスレッド API によって作成された pthread API で安全です。一方、pthread(および、あらゆるハイレベル API によって作られたスレッド上)で Mach スレッド呼び出しをするときには十分注意を払ってください。一般に、pthread 上で Mach スレッド呼び出しをするのは適切ではありません。例外はスレッド情報の取得や、スレッド例外ハンドラのセットアップ、終了通知などのための Mach API で、これらは作成方法に関係なくどんな Mach スレッドでも機能することになっています。

最後に、pthread の面ではハイレベルのスレッドのほとんどが実装されたとして、ハイレベルスレッド API を任意の pthread で使用することは安全でしょうか。たとえば、pthread 上で MP API ルーチンを呼び出しても大丈夫でしょうか。このアプローチでは確実にスレッドのレイヤ階層を壊してしまうので推奨できません。しかし 1 つ特記すべき、そして役に立つ例外があります。それは pthread 上で MP API 同期呼び出し (MPNotify/WaitOnQueue、MPSignal/WaitOnSemaphore、 MPEnter/ExitCriticalRegion、およびMPSet/WaitForEvent) をしても安全だということです。おかげでメインスレッドと Carbon の「割り込み」 とを同期させるために MP API ルーチンを使えるのでとても便利です。

先頭に戻る

Mac OS X カーネルスレッディング

従来の BSD ベースのシステムではカーネルは上下半分に分かれていました。上半分は特定のカーネル呼び出しを行ったユーザスレッドによって実行されます。I/O の完了や、共有カーネルリソースへのアクセスを待ったりしている間、ブロック(スリープ)することができます。下半分はハードウェア割り込みの結果として実行されます。割り込みはブロックしてはいけません。上半分は割り込みを無効にすることで下半分と同期しています。 McKusick, et al. は著書でこの設計に関して詳細に説明しています。

Mac OS X は BSD カーネルのソースコードの大部分を使用していますが、従来の BSD システムとは違います。その Mach の土台が BSD カーネルの同期メカニズムの改訂を要求するのです。たとえば、Mach はマルチプロセッサシステムをサポートしていて、割り込みを無効にしても、もはや同期を保証するに十分ではありません。

Mac OS X の BSD カーネルの上半分は、まだカーネル呼び出しを行ったユーザスレッドのコンテキスト内で実行されています。しかし、下半分はもはやハードウェア割り込みコンテキスト内では実行されていません。ハードウェア割り込みは Mac OS X ではごく狭い範囲しか扱っていません。ハードウェア割り込みが起こると、単に IOKit ワークループスレッドのスリープ解除を行います。これらのワークループスレッドは、 カーネルスレッドです。つまり、カーネルが所有しているのであって、BSD プロセスではありません。ワークループスレッドは BSD カーネルの下半分の実行を行うエンティティです。BSD カーネルの同期アプローチ(割り込みを無効にするために splx 呼び出しを使うこと)は、もはや Mac OS X では効果がなく、BSD カーネルの上半分と下半分は異なるプロセッサ上の異なるスレッドで実行されることも可能であることを意味します。

この問題を解決するのがカーネルファンネルです。カーネルファンネルはカーネルの BSD 部分の内部で一度に複数のスレッドが実行されるのを防ぐミューテックスです。それぞれのスレッドがカーネルの BSD 部分に入るときにカーネルファンネルを取得し、出るときには解放します。さらに、スレッドがファンネルをホールドしている間にブロック(スリープ)した場合、自動的にファンネルを手放し、他のスレッドがカーネルに入れるようにします。

Mac OS X 10.0.x はスプリットファンネルを実装しています。カーネルのネットワーキングの部分に 1 つのファンネルがあり、カーネルの他の BSD 部分(ファイルシステム、プロセス管理、デバイス管理など)にもファンネルが 1 つあります。このように分離していることにより、ディスクとネットワークの両方を使うタスクのパフォーマンスが著しく向上する結果となりました。Mac OS X の将来的なバージョンではさらに多くのファンネルを使うことも考えられ、カーネルリエントラントが改善され、さらにすぐれたパフォーマンスが期待できるかもしれません。

Mach スケジューラに関する限り、BSD カーネル内で実行されているスレッドはほかの Mach スレッドと同じです。このために起こってくる興味深い結果の 1 つに、カーネルプリエンプションがあります。優先度の高いスレッドのスリープが解除されると、BSD カーネル内で実行されているスレッドをプリエンプトするのです。これは、その優先度の高いスレッドがカーネルスレッドなのか、ユーザスレッドなのかに関係なく行われます。優先度の高いスレッドがカーネルファンネルを取得しようとしない限り(つまり BSD システム呼び出しをしないことになります)、カーネルの BSD 部分のリエントラントが制限されていてもその役目を果たします。この設計によって、Mac OS X は非常に応答性の高いオーディオプレイバックエンジンのような、リアルタイムコンポーネントが要求するリアルタイムの目標を満たすことができるのです。

先頭に戻る

要約

Mac OS X には 5 つのスレッディング API があるので複雑に思われるかもしれませんが、実際には Mac OS 9 のスレッディングよりもシンプルです。以下の基本的な概念さえ理解していただけばいいのです。

  • 究極的には、すべてのスレッドが Mach スレッドの上のレイヤにある。
  • レイヤは階層 に配置できる。
  • 異なるレイヤにあるスレッドのスレッド API をミックス するときには注意する。
  • スレッドのスケジューリングを行うときには、Mach はスレッドを作成したスレッディング API を無視する。
  • Carbon「割り込み」ルーチンはプリエンプティブなスレッドを使って シミュレート される。

参考文献

Inside Mac OS X: System Overview

DTS Q&A PS 06 Yielding Time Without Getting Events -- この Q&A では、従来の Mac OS において、なぜユーザインタフェースイベントを扱わずに他のプロセスへの明け渡しができないのかを説明しています。

DTS Q&A 1061 RunApplicationEventLoop and Thread Manager -- この Q&A では、協調的なスレッドをRunApplicationEventLoop ベースの Carbon アプリケーションに統合させる際の問題について説明しています。

DTS Technote 1104 Interrupt-Safe Routines

DTS Technote 2006 MP-Safe Routines

Carbon Specification -- MP タスクや Thread Manager コードを Mac OS X の Carbon に移植する際に直面する可能性のある問題について記述しています。

Mach 3 Kernel Principles and Interfaces -- これらの書類は古い内容も含んでいますが、今でも Mach 3 カーネルやそのプログラミング API の入門書としては価値があります。

Darwin -- Mac OS X のカーネル(CVS モジュール「xnu」)と pthread ライブラリ(CVS モジュール「Libc」)のソースコードがアップルのオープンソースの試みの 1 つとして入手できます。

The Open Group's Single UNIX Specification -- ここには pthread API の参考文献が含まれています。

David R Butenhof , Programming with POSIX Threads , Addison-Wesley, 1997, ISBN: 0201633922 -- UNIX システムの pthread プログラミングのいい入門書です。

NSThread Class -- NSThread クラスの参考文献。

Inside Macintosh: Processes -- Process Manager とその API について記述しています。

Inside Macintosh: Macintosh Toolbox Essentials -- 本書には Event Manager についての章があり、きわめて重要な WaitNextEvent 呼び出しについて記述しています。

Inside Macintosh: Thread Manager -- Mac OS 9 の Thread Manager について説明しています。

Inside Carbon: Thread Manager -- Carbon Thread Manager について説明しています。

Adding Multitasking Capability to Applications Using Multiprocessing Services -- Mac OS 9 の MP タスク API について説明しています。

Adding Multitasking Capability to Applications Using Multiprocessing Services -- Carbon の MP タスク API について説明しています。

Inside Mac OS X: Performance -- Mac OS X のスレッドのメモリコストに関する情報が含まれています。

Marshall Kirk McKusick, et al., The Design and Implementation of the 4.4BSD Operating System, Addison-Wesley, 1996, ISBN: 0201549794


先頭に戻る